from general_utilities import *
from map import *
from pathfinding import *
from actions import *

# Group-message type codes
GM_ALLRUN = 1 
GM_FOLLOWME = 2 
GM_WAITHERE = 3
GM_WAKEUP = 4

class Group_message:
  def __init__(self,type):
    # type is a GM_ constant as above
    # subclass this if you want to pack more information into the message
    self.type = type

# Priority-urgency codes
PU_OVERRIDING = 0
PU_CRITICAL = 1
PU_URGENT = 2
PU_IMPORTANT = 3
PU_MODERATE = 4
PU_MINOR = 5

# Priority-outcome codes
PO_SUCCEED = 1
PO_FAIL_AND_SCRUB = 2
PO_PASS_ON = 3

class Priority:
  # abstract class, will be subclassed heavily
  def __init__(self,brain,urgency=PU_IMPORTANT,destroy_if_succeeded=0,destroy_if_considered=0,destroy_with_prob=0,hp_less_than_this_fraction=1.0,mp_at_or_below=1e8,probability=1.0,while_condition_lasts=None,not_if_condition=None,locked=0,max_uses=1e6):
    self.brain = brain # reference up to the controlling brain
    self.entity = self.brain.entity
    self.urgency = urgency # a PU_ code as above
    self.destroy_if_succeeded = destroy_if_succeeded # if 1, remove the priority once it is fulfilled
    self.destroy_if_considered = destroy_if_considered # if 1, remove the priority after it comes up, no matter the outcome
    self.destroy_with_prob = destroy_with_prob # as per destroy_if_considered, but probabilistic
    self.probability = probability # this is the chance that the priority will not be passed by without any consideration
    self.hp_less_than_this_fraction = hp_less_than_this_fraction # only carry out priority if this holds
    self.mp_at_or_below = mp_at_or_below # only carry out priority if this holds
    self.locked = locked # if this is set, don't remove the priority when the mental state changes
    self.while_condition_lasts = while_condition_lasts # if this is set, remove the priority when this condition no longer holds
    self.not_if_condition = not_if_condition # do not activate the priority if this condition holds
    self.to_scrub = 0
    self.is_to_do_nothing = 0 # override for Priority_do_nothing
    self.max_uses = max_uses
    self.uses = 0
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    # override this in subclasses
    return PO_PASS_ON,None
    
  def __cmp__(self,other):
    if self.urgency == other.urgency:
      return 0
    elif self.urgency < other.urgency:
      return -1
    else:
      return 1
 
class Priority_attack_adjacent(Priority):
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    adjacent_enemies = []
    for enemy in self.brain.visible_enemies:
      if adjacent(d,self.entity.pos,enemy.pos):
        adjacent_enemies.append(enemy)
    if len(adjacent_enemies)==0:
      return PO_PASS_ON,None
    target = random.choice(adjacent_enemies)
    return PO_SUCCEED,Action(self.entity,A_HIT,target.pos,target)

class Priority_missile_attack(Priority):
  # needs LOS
  def __init__(self,brain,power,max_range=100,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power
    self.max_range = max_range

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    targetable_enemies = []
    for enemy in self.brain.visible_enemies:
      if d.line_of_shot(self.entity,enemy.pos,[]):
        if straight_line_distance([enemy.pos.down,enemy.pos.right],[self.entity.pos.down,self.entity.pos.right])<=self.max_range:
          targetable_enemies.append(enemy)
    if len(targetable_enemies)==0:
      return PO_PASS_ON,None
    target = random.choice(targetable_enemies)
    return PO_SUCCEED,Action(self.entity,self.power,target.pos,target)
  
class Priority_non_los_attack(Priority):
  def __init__(self,brain,power,max_range=100,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power
    self.max_range = max_range

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    targetable_enemies = []
    for enemy in self.brain.visible_enemies:
      if straight_line_distance([enemy.pos.down,enemy.pos.right],[self.entity.pos.down,self.entity.pos.right])<=self.max_range:
        targetable_enemies.append(enemy)
    if len(targetable_enemies)==0:
      return PO_PASS_ON,None
    target = random.choice(targetable_enemies)
    return PO_SUCCEED,Action(self.entity,self.power,target.pos,target)

class Priority_steal_spell(Priority):
  # monster only - cast a 'steal item' spell if legitimate
  def __init__(self,brain,power,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    if d.l.tile_at(self.entity.pos).info().can_contain_objects and d.l.tile_at(self.entity.pos).object is None:
      for enemy in self.brain.visible_enemies:
        if enemy.is_character:
          if enemy.items[0] is not None or enemy.items[1] is not None or enemy.items[2] is not None or enemy.items[3] is not None or enemy.items[4] is not None or enemy.items[5] is not None:
            return PO_SUCCEED,Action(self.entity,self.power,enemy.pos,enemy)
    return PO_PASS_ON,None
  
class Priority_heal_friend(Priority):
  def __init__(self,brain,power,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    targetable_friends = []
    for friend in self.brain.visible_friends:
      if friend.hp < friend.original_hp-2:
        targetable_friends.append(friend)
    if len(targetable_friends)==0:
      return PO_PASS_ON,None
    target = random.choice(targetable_friends)
    return PO_SUCCEED,Action(self.entity,self.power,target.pos,target)

class Priority_boost_friends(Priority):
  # some kind of boost that affects all friends in the room (subject to conditions below)
  def __init__(self,brain,power,types=None,not_if_condition=None,require_numbers=1,**parms):
    # types can be None or a [M_ code]
    Priority.__init__(self,brain,**parms)
    self.power = power
    self.types = types
    self.not_if_condition = not_if_condition
    self.require_numbers = require_numbers

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    targetable_friends = []
    for friend in self.brain.visible_friends:
      if self.types is None or friend.type in self.types:
        if self.not_if_condition is None or not friend.has_condition(self.not_if_condition):
          targetable_friends.append(friend)
    if self.not_if_condition is None or not self.entity.has_condition(self.not_if_condition):
      targetable_friends.append(self.entity)
    if len(targetable_friends)<self.require_numbers:
      return PO_PASS_ON,None
    return PO_SUCCEED,Action(self.entity,self.power,None,targetable_friends)
  
class Priority_use_power_if_meleed(Priority):
  # a non-targeted power
  def __init__(self,brain,power,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    adjacent_enemies = []
    for enemy in self.brain.visible_enemies:
      if adjacent(d,self.entity.pos,enemy.pos):
        return PO_SUCCEED,Action(self.entity,self.power,self.entity.pos,None)
    return PO_PASS_ON,None
  
class Priority_approach_enemy(Priority):
  def __init__(self,brain,chase_to_new_room=1,**parms):
    Priority.__init__(self,brain,**parms)
    self.path = None # will be filled in with a Path object later
    self.chase_to_new_room = chase_to_new_room # should it?
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    # Could be improved - poor decision routine for which unseen opponent to move towards
    # Need to think this through properly. Only good way of deciding which opponent is closest is to use pathfinder but this is expensive...
    nearest_enemy = None
    distance_plus_time = 1e8
    enemy_ids_seen = []
    for enemy in self.brain.visible_enemies:
      if self.chase_to_new_room or enemy.pos.same_room(self.entity.pos):
        enemy_ids_seen.append(enemy.id)
        if self.entity.pos.distance_within_room(enemy.pos) < distance_plus_time:
          nearest_enemy = enemy
          distance_plus_time = self.entity.pos.distance_within_room(enemy.pos)
          visible = 1
    enemies_seen_recently = d.get_group(self.entity.id.group).enemies_seen_recently
    for enemy_id in enemies_seen_recently.keys(): # key is entity id, value is [Pos,turn]
      if not enemy_id in enemy_ids_seen:
        this_enemy = d.get_entity(enemy_id)
        if not this_enemy.no_longer_here(): # do i really need this? 
          this_enemy_pos = enemies_seen_recently[enemy_id][0]
          if self.chase_to_new_room: # or this_enemy_pos.same_room(self.entity.pos):
            this_distance = ai_distance(d,self.entity.pos,this_enemy_pos)
            this_time = (d.turn-enemies_seen_recently[enemy_id][1])
            if this_distance + this_time < distance_plus_time:
              nearest_enemy = this_enemy
              distance_plus_time = this_distance + this_time
              visible = 0
    if nearest_enemy is None:
      return PO_PASS_ON,None
    if not nearest_enemy.pos.same_room(self.entity.pos) and not self.chase_to_new_room:
      raise SystemError
    steps_to_avoid = []
    for friend in self.brain.visible_friends:
      steps_to_avoid.append(friend.pos)
    fiery_steps_to_avoid = get_fiery_steps_to_avoid(d,self.entity)
    if visible and d.line_of_shot(self.entity,nearest_enemy.pos,[],0,0):
      self.path = None # no need to use the pathfinder, just take a step
      step = step_with_regard_to(d,self.entity,steps_to_avoid+fiery_steps_to_avoid,[nearest_enemy.pos],None)
    else:
      if self.path is None:
        self.path = Path(d,self.entity.pos,nearest_enemy.pos,fiery_steps_to_avoid,steps_to_avoid,self.entity)
      else:
        self.path.change_destination(d,self.entity.pos,nearest_enemy.pos)
      step = self.path.pop_next_step(d,self.entity.pos,[],fiery_steps_to_avoid+steps_to_avoid)
    if step is None:
      return PO_PASS_ON,None
    else:
      return PO_SUCCEED,make_basic_move_action(d,self.entity,step)

class Priority_move_to_closest_friend(Priority):
  def __init__(self,brain,type,**parms):
    Priority.__init__(self,brain,**parms)
    self.type = type # a M_ code or None (ie. any friend)
    self.path = None # will be filled in with a Path object later
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    nearest_friend = None
    distance = 1e8
    visible = 0
    for friend in d.get_group(self.entity.id.group).m:
      if self.type is None or self.type==friend.type:
        if self.entity<>friend:
          if ai_distance(d,self.entity.pos,friend.pos) < distance:
            nearest_friend = friend
            distance = ai_distance(d,self.entity.pos,friend.pos)
            if self.entity.pos.same_room(friend.pos):
              visible = 1
            else:
              visible = 0
    if nearest_friend is None or distance>40 or distance<2:
      return PO_PASS_ON,None
    steps_to_avoid = []
    fiery_steps_to_avoid = get_fiery_steps_to_avoid(d,self.entity)
    if visible and d.line_of_shot(self.entity,nearest_friend.pos,[],0,0):
      self.path = None # no need to use the pathfinder, just take a step
      step = step_with_regard_to(d,self.entity,steps_to_avoid+fiery_steps_to_avoid,[nearest_friend.pos],None)
    else:
      if self.path is None:
        self.path = Path(d,self.entity.pos,nearest_friend.pos,fiery_steps_to_avoid,steps_to_avoid,self.entity)
      else:
        self.path.change_destination(d,self.entity.pos,nearest_friend.pos)
      step = self.path.pop_next_step(d,self.entity.pos,[],fiery_steps_to_avoid+steps_to_avoid)
    if step is None:
      return PO_PASS_ON,None
    else:
      return PO_SUCCEED,make_basic_move_action(d,self.entity,step)

class Priority_step_randomly(Priority):
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    count = 0
    a = None
    for dir in permute(DIRS_AROUND):
      new_pos = get_pos_in_dir(d,self.entity.pos,dir)
      a = make_basic_move_action(d,self.entity,new_pos)
      if a.type==A_NOTHING or a.type==A_USE_PORTAL:
        continue
      if (d.l.tile_at(new_pos).info().is_fiery or d.l.is_temporary_fire(new_pos)) and self.entity.info().avoids_fire:
        continue
      if (d.l.tile_at(new_pos).info().fall_into) and self.entity.info().avoids_pits:
        continue
      break
    if a is None:
      return PO_PASS_ON,None
    else:
      return PO_SUCCEED,a

class Priority_boost_self(Priority):
  # target this power on yourself, only if character is in same room
  def __init__(self,brain,power,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    if self.entity.pos.same_room(d.get_active_pos()):
      return PO_SUCCEED,Action(self.entity,self.power,self.entity.pos,None)        
    return PO_PASS_ON,None

class Priority_power_on_adjacent(Priority):
  # target this power on a random adjacent space - must be shootable and unoccupied
  def __init__(self,brain,power,**parms):
    Priority.__init__(self,brain,**parms)
    self.power = power

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    count = 0
    for dir in permute(DIRS_AROUND):
      new_pos = get_pos_in_dir(d,self.entity.pos,dir)
      if d.l.valid(new_pos) and d.l.tile_at(new_pos).info().shootable:
        occupant = d.get_occupant(new_pos)
        if occupant is None:
          return PO_SUCCEED,Action(self.entity,self.power,new_pos,None)        
    return PO_PASS_ON,None
  
class Priority_move_around_opponent(Priority):
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    count = 0
    a = None
    for dir in permute(DIRS_AROUND):
      new_pos = get_pos_in_dir(d,self.entity.pos,dir)
      if new_pos is None:
        continue
      if (d.l.tile_at(new_pos).info().is_fiery or d.l.is_temporary_fire(new_pos)) and self.entity.info().avoids_fire:
        continue
      if d.l.tile_at(new_pos).info().fall_into and self.entity.info().avoids_pits:
        continue
      adjacent_to_some_enemy = 0
      for enemy in self.brain.visible_enemies:
        if adjacent(d,new_pos,enemy.pos):
          adjacent_to_some_enemy = 1
      if not adjacent_to_some_enemy:
        continue
      a = make_basic_move_action(d,self.entity,new_pos)
      if a.type is not A_NOTHING:
        break
    if a is None:
      return PO_PASS_ON,None
    else:
      return PO_SUCCEED,a
    
class Priority_do_nothing(Priority):
  def __init__(self,brain,**parms):
    Priority.__init__(self,brain,**parms)
    self.is_to_do_nothing = 1
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    return PO_SUCCEED, Action(self.entity,A_NOTHING,None,None)

class Priority_avoid_getting_too_close(Priority):
  def __init__(self,brain,min_range,**parms):
    Priority.__init__(self,brain,**parms)
    self.min_range = min_range

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    nearest_enemy = None
    distance = 1e8
    enemy_posses = []
    for enemy in self.brain.visible_enemies:
      enemy_posses.append(self.entity.pos)
      if self.entity.pos.distance_within_room(enemy.pos) < distance:
        nearest_enemy = enemy
        distance = self.entity.pos.distance_within_room(enemy.pos)
    if distance >= self.min_range:
      return PO_PASS_ON, None
    fiery_steps_to_avoid = get_fiery_steps_to_avoid(d,self.entity)
    step = step_with_regard_to(d,self.entity,fiery_steps_to_avoid,None,[nearest_enemy.pos])
    if step is not None and step<>self.entity.pos:
      return PO_SUCCEED,make_basic_move_action(d,self.entity,step)
    return PO_PASS_ON,None

class Priority_waypoint_wander(Priority):
  def __init__(self,brain,**parms):
    Priority.__init__(self,brain,**parms)
    self.path = None # will be filled in with a Path object later
    self.last_portal_entered = None
    
  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    exits = []
    this_room = d.l.room_at(self.entity.pos)
    for dir in [DIR_UP,DIR_DOWN,DIR_LEFT,DIR_RIGHT]:
      door_pos = this_room.get_mid_wall(dir)
      if door_pos in this_room.doors_pos:
        if d.l.tile_at(door_pos).info().walkable(self.entity):
          exits.append(door_pos)
    if this_room.stairs_up_pos is not None:
      exits.append(this_room.stairs_up_pos)
    if this_room.stairs_down_pos is not None:
      exits.append(this_room.stairs_down_pos)
    exits_not_to_consider = []
    if self.last_portal_entered is not None:
      exits_not_to_consider.append(d.l.tile_at(self.last_portal_entered).to_pos)
    redone_path = 0
    while not redone_path:
      if self.path is None:
        redone_path = 1
        exit_options = []
        for exit_pos in exits:
          if not(exit_pos in exits_not_to_consider):
            exit_options.append(exit_pos)
        if len(exit_options)==0:
          break
        destination = random.choice(exit_options)
        fiery_steps_to_avoid = get_fiery_steps_to_avoid(d,self.entity)
        self.path = Path(d,self.entity.pos,destination,fiery_steps_to_avoid,[],self.entity)
        if self.path is None:
          exit_options.append(exit_pos)
          continue
      step = self.path.pop_next_step(d,self.entity.pos,[],[])
      if step is None:
        self.path = None
      else:
        move_action = make_basic_move_action(d,self.entity,step)
        if move_action.type==A_USE_PORTAL:
          self.path = None
          self.last_portal_entered = self.entity.pos
        return PO_SUCCEED,move_action  
    return PO_PASS_ON,None
  
class Priority_run_away(Priority):
  def __init__(self,brain,**parms):
    Priority.__init__(self,brain,**parms)
    self.path = None # will be filled in with a Path object later
    self.stop_running_after_next_portal = 0
    self.stopped_running = 0
    self.stop_running_in = -1
    self.last_portal_passed = None

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    nearest_enemy = None
    distance_plus_time = 1e8
    enemy_ids_seen = []
    danger_posses = []
    for enemy in self.brain.visible_enemies:
      enemy_ids_seen.append(enemy.id)
      danger_posses.append(enemy.pos)
      for dir in DIRS_AROUND:
        this_danger_pos = get_pos_in_dir(d,enemy.pos,dir)
        if d.l.valid(this_danger_pos):
          danger_posses.append(this_danger_pos)
      if self.entity.pos.distance_within_room(enemy.pos) < distance_plus_time:
        nearest_enemy = enemy
        distance_plus_time = self.entity.pos.distance_within_room(enemy.pos)
    if nearest_enemy is None:
      if self.stop_running_in>=0:
        self.stop_running_in = self.stop_running_in-1
      if self.stop_running_in==0:
        self.stop_running = 1
      if self.stopped_running==1:
        self.path = None
        random_step = Priority_step_randomly(self.brain) 
        random_action = random_step.get_action(d)
        if random_action[1].type==A_USE_PORTAL:
          return PO_SUCCEED, Action(self.entity,A_NOTHING,None,None)
        else:
          return random_action
    if nearest_enemy is not None:
      self.stop_running_after_next_portal = 0
      self.stopped_running = 0
      self.stop_running_in = -1
    if not self.entity.is_character and d.l.tile_at(self.entity.pos).info().is_monster_exit:
      return PO_SUCCEED,Action(self.entity,A_USE_EXIT,None,None)
    steps_to_avoid = []
    for friend in self.brain.visible_friends:
      steps_to_avoid.append(friend.pos)
    fiery_steps_to_avoid = get_fiery_steps_to_avoid(d,self.entity)
    redone_path = 0
    print "leprechaun - trying"
    while not redone_path:
      dont_use_dirs = []
      if self.last_portal_passed is not None:
        passed_via = d.l.tile_at(self.last_portal_passed).to_pos
        danger_posses.append(passed_via)
      if self.path is None:
        redone_path = 1
        self.path = go_for_nearby_exit(d,self.entity,self.entity.pos.copy(),0,self.entity.is_character==0,danger_posses+fiery_steps_to_avoid,[],[])
        if self.path is None:
          print "leprechaun - failed go_for_nearby_exit"
          break
      step = self.path.pop_next_step(d,self.entity.pos,[],steps_to_avoid+danger_posses+fiery_steps_to_avoid)
      if step is None or step==self.entity.pos:
        self.path = None
      else:
        move_action = make_basic_move_action(d,self.entity,step)
        if move_action.type==A_USE_PORTAL:
          self.path = None
          self.last_portal_passed = self.entity.pos
          if self.stop_running_after_next_portal:
            self.stop_running_in = 5
          else:
            self.stop_running_after_next_portal = 1
        if move_action.type==A_NOTHING or (move_action.type==A_WALK and step==self.entity.pos):
          print "leprechaun - no step (a)"
          return PO_PASS_ON,None
        else:
          print "leprechaun - found good step"
          return PO_SUCCEED,move_action
    self.path = None # can't use the pathfinder, well just take a step
    if nearest_enemy is not None:    
      step = step_with_regard_to(d,self.entity,steps_to_avoid+fiery_steps_to_avoid,None,[nearest_enemy.pos])
      if step is not None and step<>self.entity.pos:
        print "leprechaun - found adequate step"
        return PO_SUCCEED,make_basic_move_action(d,self.entity,step)
      else:
        print "leprechaun - no step at all"
    return PO_PASS_ON,None

class Priority_make_noise(Priority):
  def __init__(self,brain,type=None,message=None,volume=10,sound=None,**params):
    Priority.__init__(self,brain,**params)
    self.type = type
    self.message = message
    self.volume = volume
    self.sound = sound

  def get_action(self,d):
    # return Priority_outcome, Action (or None)
    # but in this case called for the side effect!
    n = Notification(self.entity.pos)
    if self.sound is not None:
      n.set_sound(self.sound)
    if self.type is not None:
      n.set_noise(1,self.type,self.volume,self.entity,1,1)
    if self.message is not None:
      n.set_message(self.message,0)
    n.play(d)
    return PO_PASS_ON,None    
  
ST_UNINITIALIZED = 0 # brain state only used for monsters who have just been constructed 
                     # and whose AI hasn't been set up yet
ST_NORMAL = 1 # basic 'nothing special' brain state
ST_WAITING = 2 # character 'standing around' state
ST_FOLLOWING = 3 # character 'following leader' state
ST_ASLEEP = 4 # zz

class Basic_brain:
  # will be subclassed, especially 'assess_situation'
  def __init__(self,d,entity,initial_state=ST_UNINITIALIZED):
    self.entity = entity
    self.messages_received = [] # fill this with Group_messages as they arrive, appending to the end
                                # remove them as they are noted
    self.state = initial_state # a ST_ constant as above
    self.priorities = [] # a [Priority], not maintained in any particular order
    self.visible_enemies = [] # will be replaced with an [Entity]
    self.visible_friends = [] # will be replaced with an [Entity]
    self.disturbed = 0 # set to 1 when hit, etc
    self.eyes_shut = 0 # if this is 1, no response to visible enemies in same room
    self.disturbed_in_turn = None # set to current turn when some stimulus happens
    self.set_move_sound()
  
  def get_visible_enemies(self,d):
    # return a [Entity]
    result = []
    for entity in d.get_entities_in_room(self.entity.pos):        
      if (not entity.no_longer_here()) and entity is not self.entity:
        if self.entity.can_see_if_in_los(d,entity):
          if self.entity.is_enemy(entity):
            result.append(entity)
    return result
  
  def get_visible_friends(self,d):
    # return a [Entity]
    result = []
    for entity in d.get_entities_in_room(self.entity.pos):        
      if (not entity.no_longer_here()) and entity is not self.entity:
        if self.entity.can_see_if_in_los(d,entity):
          if not self.entity.is_enemy(entity):
            result.append(entity)
    return result
          
  def send_group_message(self,d,type):
    # pass a group_message, send it to all in the group (including self)
    # currently this only allows for a single type parameter - change later as needed? or create more similar fns?
    for entity in d.get_group(self.entity.id.group).m:
      entity.brain.messages_received.append(Group_message(type))
      
  def get_action(self,d):
    # call each time the entity has the chance to act
    # return an action
    # typically will stay the same in subclassed versions
    self.assess_situation(d) # change state etc - will be modified in subclasses
    if len(self.priorities)==0:
      return Action(self.entity,A_NOTHING,None,None)
    if len(self.priorities)==1 and self.priorities[0].is_to_do_nothing:
      return Action(self.entity,A_NOTHING,None,None)
    self.visible_enemies = self.get_visible_enemies(d) # gather information
    self.visible_friends = self.get_visible_friends(d) 
    # from here on, decide an action and edit the priority queue as necessary
    action = None
    self.priorities.sort() # does this get the ordering right? Check
    for priority in self.priorities:
      if priority.probability < 1:
        if random.random()>priority.probability:
          continue
      if priority.while_condition_lasts is not None:
        if not self.entity.has_condition(priority.while_condition_lasts):
          priority.to_scrub = 1
          continue
      if priority.not_if_condition is not None:
        if self.entity.has_condition(priority.not_if_condition):
          continue
      if priority.hp_less_than_this_fraction < 1.0:
        if float(self.entity.hp)/self.entity.original_hp > priority.hp_less_than_this_fraction:
          continue
      if priority.mp_at_or_below < self.entity.mp:
        continue
      outcome,action = priority.get_action(d)
      if (outcome==PO_SUCCEED and priority.destroy_if_succeeded) or (outcome==PO_FAIL_AND_SCRUB) or (priority.destroy_if_considered) or (random.random() < priority.destroy_with_prob):
        priority.to_scrub = 1
      if outcome==PO_SUCCEED:
        priority.uses = priority.uses + 1
        if priority.uses >= priority.max_uses:
          priority.to_scrub = 1
        break
    # remove the defunct priorities 
    check_list = self.priorities[:] 
    for priority in check_list:
      if priority.to_scrub:
        self.priorities.remove(priority)
    # and return the decision
    if action is None:
       return Action(self.entity,A_NOTHING,None,None)
    else:
      return action

  def clear_priorities(self):
    # get rid of anything that's not 'locked'
    for priority in self.priorities:
      if not priority.locked:
        priority.to_scrub = 1
    check_list = self.priorities[:] 
    for priority in check_list:
      if priority.to_scrub:
        self.priorities.remove(priority)
    
  def assess_situation(self,d):
    # will change in subclassed versions
    pass

  def set_move_sound(self,message=None,type=SD_MOVE,volume=10):
    self.move_sound = type
    self.move_volume = volume
    self.move_message = message
  
class Dumb_monster_brain(Basic_brain):
  def assess_situation(self,d):
    # If copying this, make sure to honor the 'locked' argument
    if GM_WAKEUP in self.messages_received:
      self.disturbed = 1
      while GM_WAKEUP in self.messages_received:
        self.messages_received.remove(GM_WAKEUP) 
    wake_up_if_asleep = 0
    if self.disturbed:
      self.disturbed_in_turn = d.turn
      wake_up_if_asleep = 1
      self.disturbed = 0 # so that future disturbances reset self.disturbed_in_turn
    if (self.state==ST_UNINITIALIZED or (self.eyes_shut==0 and self.disturbed_in_turn <= d.turn - 30)) and not wake_up_if_asleep and self.entity.pos.level in d.levels_visited:
      self.state=ST_ASLEEP
      self.entity.vulnerable=1
      self.eyes_shut=1
      self.clear_priorities()
      self.set_sleeping_priorities()
    elif (self.state==ST_ASLEEP or self.state==ST_UNINITIALIZED) and wake_up_if_asleep:
      self.wakeup_call(d)
      self.state=ST_NORMAL
      self.entity.vulnerable=0
      self.eyes_shut=0
      self.send_group_message(d,GM_WAKEUP)
      self.clear_priorities()
      self.set_waking_priorities()
    
  def set_waking_priorities(self,chase_to_new_room=1):
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,chase_to_new_room=chase_to_new_room,urgency=PU_IMPORTANT))
    self.priorities.append(Priority_step_randomly(self,urgency=PU_MINOR))

  def set_sleeping_priorities(self):
      self.priorities.append(Priority_do_nothing(self,urgency=PU_URGENT))
  
  def wakeup_call(self,d):
    pass # can override this

  def death_message(self,d):
    # can override this
    n = Notification(self.entity.pos)
    n.set_message(self.death_text(),0)
    death_icon = self.death_flash()
    if death_icon is not None:
      n.set_flash(self.entity.pos,self.death_flash(),0.1)
    n.play(d)     
    
  def death_text(self):
    # can override this
    return self.entity.name+" dies..."
  
  def death_flash(self):
    # can override this
    return display.misc_skeleton  
  
  def fade_out(self):
    # can override this
    return 0
 
class Shambler_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    self.priorities.append(Priority_step_randomly(self,urgency=PU_CRITICAL,probability=0.2))      
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" makes a rustling sound...",type=SD_RUSTLE,volume=5))      
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT,chase_to_new_room=0))
    self.priorities.append(Priority_step_randomly(self,urgency=PU_MINOR,probability=0.8))   

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" makes a rustling sound...",type=SD_RUSTLE,volume=5))      
    self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.8))      

  def death_text(self):
    return self.entity.name+" is destroyed..."
  
  def death_flash(self):
    return None

class Chameleon_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)  
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,type=SD_HISS,volume=10,sound='hiss'))         

  def death_flash(self):
    return display.animal_skeleton

class Stalker_brain(Dumb_monster_brain):
  def __init__(self,d,entity):
    # starts awake
    Dumb_monster_brain.__init__(self,d,entity)
    self.disturbed = 1
    
  def set_waking_priorities(self):
    self.priorities.append(Priority_step_randomly(self,urgency=PU_CRITICAL,probability=0.2))      
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT)) 

  def death_text(self):
    return self.entity.name+" is destroyed..."
  
  def death_flash(self):
    return None
  
class Skeleton_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    # no random stepping    
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT))
    self.set_move_sound(message=self.entity.name+" clatters towards you.",type=SD_CLATTER,volume=10)

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_CLATTER,10,self.entity,1,1)
    n.set_message(self.entity.name+' pulls out a rusty sword and advances, teeth clacking.',0)
    n.play(d)

  def death_text(self):
    return self.entity.name+" is destroyed..."
  
  def death_flash(self):
    return display.broken_bone
  
class Salamander_brain(Dumb_monster_brain):  
  def __init__(self,d,entity):
    # starts awake
    Dumb_monster_brain.__init__(self,d,entity)
    self.disturbed = 1
    
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self,chase_to_new_room=0)  
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" hisses at you.",type=SD_HISS,volume=12))      
  
  def death_flash(self):
    return display.firetendrils

class Sea_serpent_brain(Dumb_monster_brain):  
  def __init__(self,d,entity):
    # starts awake
    Dumb_monster_brain.__init__(self,d,entity)
    self.disturbed = 1
  
  def death_flash(self):
    return display.animal_skeleton
  
class Goblin_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,message=self.entity.name+" yells \"DADDU-YADDU!\"",type=SD_DADDU_YADDU,volume=12))      

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,message=self.entity.name+" yells \"DADDU-YADDU!\"",type=SD_DADDU_YADDU,volume=12))      
    # self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.3))      

class Ogre_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.set_move_sound(message=self.entity.name+" roars: 'I'll crush your bones to make my bread!'",type=SD_BIGFEET,volume=20)

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" lets out an enormous snore!",type=SD_SNORE,volume=20))      

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_ROAR,30,self.entity,1,1)
    n.set_sound("083-Monster05")
    n.set_message(self.entity.name+' suddenly stops snoring. It roars in anger, stands up and picks up its club.',0)
    n.play(d)

  def death_message(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+" lets out a roar and slowly collapses to the ground, dead...",0)
    n.set_sound("083-Monster05")    
    n.set_flash(self.entity.pos,self.death_flash(),0.1)
    n.play(d)    
  
class Goblinarcher_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self,chase_to_new_room=0)
    self.priorities.append(Priority_avoid_getting_too_close(self,urgency=PU_CRITICAL,min_range=4,max_uses=50))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_URGENT,power=A_SHOT))
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,message=self.entity.name+" yells \"DADDU-YADDU!\"",type=SD_DADDU_YADDU,volume=12))      

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,message=self.entity.name+" yells \"DADDU-YADDU!\"",type=SD_DADDU_YADDU,volume=12))      
    # self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.3))      

class Apprentice_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_use_power_if_meleed(self,urgency=PU_CRITICAL,probability=0.2,power=A_BLINK,max_uses=3))
    self.priorities.append(Priority_avoid_getting_too_close(self,urgency=PU_CRITICAL,min_range=4,probability=0.3))
    self.priorities.append(Priority_non_los_attack(self,urgency=PU_IMPORTANT,power=A_CONFUSION,probability=0.05,max_uses=5))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_MODERATE,power=A_ZAP,probability=0.5))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_MINOR))
    self.priorities.append(Priority_run_away(self,urgency=PU_CRITICAL,mp_at_or_below=action_mana_costs[A_ZAP]))
    self.priorities.append(Priority_do_nothing(self,urgency=PU_IMPORTANT,mp_at_or_below=action_mana_costs[A_ZAP]))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' looks around and sees you. "Zappus Intruderus!" he yells frantically.',0)
    n.play(d)

class Sorcerer_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_use_power_if_meleed(self,urgency=PU_CRITICAL,probability=0.2,power=A_BLINK))
    self.priorities.append(Priority_use_power_if_meleed(self,urgency=PU_CRITICAL,probability=0.3,power=A_TELEPORT_LONG,hp_less_than_this_fraction=0.5,max_uses=2))
    self.priorities.append(Priority_avoid_getting_too_close(self,urgency=PU_CRITICAL,min_range=4,probability=0.3))
    self.priorities.append(Priority_non_los_attack(self,urgency=PU_IMPORTANT,power=A_CONFUSION,probability=0.05,max_uses=10))
    self.priorities.append(Priority_steal_spell(self,urgency=PU_IMPORTANT,power=A_STEAL_ITEM_SPELL,probability=0.08))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_MODERATE,power=A_ZAP))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_IMPORTANT,power=A_FIREBOLT,probability=0.2))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_IMPORTANT,power=A_WINDBLAST,probability=0.2))
    self.priorities.append(Priority_boost_self(self,urgency=PU_IMPORTANT,power=A_INVISIBILITY,probability=0.1,not_if_condition=C_INVISIBLE,max_uses=3))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_MINOR))
    self.priorities.append(Priority_run_away(self,urgency=PU_CRITICAL,mp_at_or_below=action_mana_costs[A_ZAP]))
    self.priorities.append(Priority_do_nothing(self,urgency=PU_IMPORTANT,mp_at_or_below=action_mana_costs[A_ZAP]))
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" yells \"I'll turn you into a cockroach and step on you!\"",type=SD_CAST,volume=10))      

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' notices you. He closes his book and stands up with a frown of displeasure.',0)
    n.play(d)

class Darkelf_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_use_power_if_meleed(self,urgency=PU_CRITICAL,probability=0.2,power=A_BLINK,hp_less_than_this_fraction=0.5))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_IMPORTANT,power=A_LIGHTNING_BOLT,probability=0.2,max_uses=3))
    self.priorities.append(Priority_boost_self(self,urgency=PU_IMPORTANT,power=A_INVISIBILITY,probability=0.1,not_if_condition=C_INVISIBLE))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_MINOR))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' pulls two curved swords from their sheaths and issues a challenge!',0)
    n.play(d)

  def death_text(self):
    return self.entity.name+" falls to his knees and curses you, then slumps to the ground, dead."  
    
class Firedrake_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_power_on_adjacent(self,urgency=PU_CRITICAL,power=A_BIG_FIREBREATH,destroy_if_succeeded=1))
    self.priorities.append(Priority_missile_attack(self,urgency=PU_URGENT,power=A_BIG_FIREBREATH,max_range=8,probability=0.3))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_IMPORTANT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_MODERATE))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' opens one eye. It has seen you! It roars with anger and lets out a great gout of flames.',0)
    n.set_sound("roar")
    n.play(d)
    wait(ms=1500)
    
  def death_message(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+" rears up and roars one last time. Like a blazing comet, it crashes to the ground - dead!",0,color=red)
    n.set_sound("roar")    
    n.play(d)   
    wait(ms=2000)

class Goblinshaman_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    # TO DO: Something here causes door-oscillation if the player is nearby. I suspect the combination of Priority_avoid_getting_too_close and Priority_approach_enemy.
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.2,message=self.entity.name+" yells \"IKKA-IKKA-UKKA-YUKKA!\" dementedly.",type=SD_DADDU_YADDU,volume=12))      
    self.priorities.append(Priority_boost_friends(self,urgency=PU_MODERATE,types=[M_GOBLIN,M_GOBLIN_ARCHER,M_GOBLIN_SHAMAN],power=A_BLESSING,not_if_condition=C_BLESSED,require_numbers=2))
    self.priorities.append(Priority_heal_friend(self,urgency=PU_MODERATE,power=A_HEALING,probability=0.5))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_MINOR,probability=0.3,chase_to_new_room=0))
    self.priorities.append(Priority_do_nothing(self,urgency=PU_IMPORTANT,mp_at_or_below=5))

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" throws the sacred bones of divination!",type=SD_DADDU_YADDU,volume=12))      
    self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.6)) 
    
class Brigand_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.priorities.append(Priority_missile_attack(self,urgency=PU_URGENT,power=A_SHOT))

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_waypoint_wander(self,urgency=PU_URGENT)) 

class Bat_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self,chase_to_new_room=0)
    self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.2))      
    self.priorities.append(Priority_move_around_opponent(self,urgency=PU_CRITICAL,probability=0.5))      

  def death_flash(self):
    return None

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_sound('bat')
    n.set_message(self.entity.name+' flaps towards you...',0)
    n.play(d)
    
class Statue_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))     

  def death_text(self):
    return self.entity.name+" is destroyed..."
  
  def death_flash(self):
    return None
  
class Harpy_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.priorities.append(Priority_run_away(self,urgency=PU_CRITICAL,hp_less_than_this_fraction=0.4))         
    self.priorities.append(Priority_do_nothing(self,urgency=PU_URGENT,hp_less_than_this_fraction=0.4))         
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" gibbers at you!",type=SD_SCREECH,volume=12))      

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_SCREECH,15,self.entity,1,1)
    n.set_sound('pain')
    n.set_message(self.entity.name+' screeches vilely and flaps towards you...',0)
    n.play(d)

class Spider_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT,chase_to_new_room=0))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' begins to creep towards you on its vile, hairy, clawed legs...',0)
    n.play(d)

  def death_flash(self):
    return None

class Snake_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT,destroy_if_succeeded=1))
    self.priorities.append(Priority_run_away(self,urgency=PU_IMPORTANT))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_MINOR))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_HISS,10,self.entity,1,1)
    n.set_message(self.entity.name+' hisses warningly at you.',0)
    n.set_sound('snake')
    n.play(d)

  def death_flash(self):
    return None
  
class Leprechaun_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    self.priorities.append(Priority_run_away(self,urgency=PU_URGENT))         
    self.priorities.append(Priority_use_power_if_meleed(self,urgency=PU_CRITICAL,probability=0.5,power=A_BLINK))
    self.set_move_sound(message=self.entity.name+" says 'You'll not get a penny of me gold!'",volume=0)

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_message(self.entity.name+' says "Faith an\' begorrah! I\'ll be on me way then!"',0)
    n.play(d)
    
class Firelizard_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    self.priorities.append(Priority_missile_attack(self,urgency=PU_CRITICAL,power=A_FIREBREATH,max_range=5,probability=0.3))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT))
    self.priorities.append(Priority_step_randomly(self,urgency=PU_MINOR,probability=0.8))   

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_power_on_adjacent(self,urgency=PU_IMPORTANT,power=A_FIREBREATH,probability=0.3))
    self.priorities.append(Priority_step_randomly(self,urgency=PU_MINOR,probability=0.8))   

  def death_flash(self):
    return display.animal_skeleton

class Wight_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    self.priorities.append(Priority_non_los_attack(self,urgency=PU_CRITICAL,power=A_FEAR,max_range=3.5,probability=0.25))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT))

  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_CLATTER,10,self.entity,1,1)
    n.set_message(self.entity.name+' rises from its tomb. It will slay you for disturbing its eternal sleep!',0)
    n.play(d)

  def death_flash(self):
    return None
  
  def fade_out(self):
    return 1

class Wraith_brain(Dumb_monster_brain):
  def set_waking_priorities(self):
    self.priorities.append(Priority_non_los_attack(self,urgency=PU_CRITICAL,power=A_FEAR,probability=0.1,max_uses=7))
    self.priorities.append(Priority_attack_adjacent(self,urgency=PU_URGENT))
    self.priorities.append(Priority_approach_enemy(self,urgency=PU_IMPORTANT))

  def death_flash(self):
    return None
  
  def fade_out(self):
    return 1

class Orc_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" growls ferociously.",type=SD_GRUNT,volume=12,sound="orc_grunt"))      
    self.priorities.append(Priority_move_to_closest_friend(self,urgency=PU_MINOR,type=M_ORC))           
    self.priorities.append(Priority_move_to_closest_friend(self,urgency=PU_MODERATE,type=M_ORC_CAPTAIN))           

  def set_sleeping_priorities(self):
    self.priorities.append(Priority_step_randomly(self,urgency=PU_URGENT,probability=0.2))      
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.05,message=self.entity.name+" grunts moodily.",type=SD_GRUNT,volume=12,sound="grunt"))      
    self.priorities.append(Priority_move_to_closest_friend(self,urgency=PU_MODERATE,type=M_ORC_CAPTAIN)) 
    self.priorities.append(Priority_move_to_closest_friend(self,urgency=PU_MINOR,type=M_ORC)) 
    
  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_GRUNT,10,self.entity,1,1)
    n.set_sound("orc_grunt")
    n.set_message(self.entity.name+' grunts bestially and lugs out a rusty sword.',0)
    n.play(d)
    
class Orccaptain_brain(Dumb_monster_brain):  
  def set_waking_priorities(self):
    Dumb_monster_brain.set_waking_priorities(self)
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" barks out an order.",type=SD_GRUNT,volume=12,sound="orc_grunt"))         
    self.priorities.append(Priority_missile_attack(self,urgency=PU_URGENT,power=A_SHOT,probability=0.5))
    
  def set_sleeping_priorities(self):
    self.priorities.append(Priority_waypoint_wander(self,urgency=PU_URGENT))      
    self.priorities.append(Priority_make_noise(self,urgency=PU_CRITICAL,probability=0.1,message=self.entity.name+" barks out an order.",type=SD_GRUNT,volume=12,sound="grunt"))      
    
  def wakeup_call(self,d):
    n = Notification(self.entity.pos)
    n.set_noise(1,SD_GRUNT,10,self.entity,1,1)
    n.set_sound("orc_grunt")
    n.set_message(self.entity.name+' looks round and yells "Get\' im, boys!"',0)
    n.play(d)

def make_brain(d,entity):
  # return a brain object
  if entity.type==M_SKELETON:
    return Skeleton_brain(d,entity)
  elif entity.type==M_GOBLIN:
    return Goblin_brain(d,entity)
  elif entity.type==M_SHAMBLER:
    return Shambler_brain(d,entity)
  elif entity.type==M_BAT:
    return Bat_brain(d,entity)
  elif entity.type==M_HARPY:
    return Harpy_brain(d,entity)
  elif entity.type==M_LEPRECHAUN:
    return Leprechaun_brain(d,entity)
  elif entity.type==M_INVISIBLE_STALKER:
    return Stalker_brain(d,entity)
  elif entity.type==M_STATUE:
    return Statue_brain(d,entity)
  elif entity.type==M_CHAMELEON:
    return Chameleon_brain(d,entity)
  elif entity.type==M_FIRE_LIZARD:
    return Firelizard_brain(d,entity)
  elif entity.type==M_GOBLIN_ARCHER:
    return Goblinarcher_brain(d,entity)
  elif entity.type==M_BRIGAND:
    return Brigand_brain(d,entity)
  elif entity.type==M_OGRE:
    return Ogre_brain(d,entity)
  elif entity.type==M_SALAMANDER:
    return Salamander_brain(d,entity)
  elif entity.type==M_SEA_SERPENT:
    return Sea_serpent_brain(d,entity)
  elif entity.type==M_APPRENTICE:
    return Apprentice_brain(d,entity)
  elif entity.type==M_GOBLIN_SHAMAN:
    return Goblinshaman_brain(d,entity)
  elif entity.type==M_WIGHT:
    return Wight_brain(d,entity)
  elif entity.type==M_WRAITH:
    return Wraith_brain(d,entity)
  elif entity.type==M_SPIDER:
    return Spider_brain(d,entity)
  elif entity.type==M_SNAKE:
    return Snake_brain(d,entity)
  elif entity.type==M_SORCERER:
    return Sorcerer_brain(d,entity)
  elif entity.type==M_ORC:
    return Orc_brain(d,entity)
  elif entity.type==M_DARK_ELF:
    return Darkelf_brain(d,entity)
  elif entity.type==M_FIREDRAKE:
    return Firedrake_brain(d,entity)
  elif entity.type==M_ORC_CAPTAIN:
    return Orccaptain_brain(d,entity)
  else:
    print "Unknown brain"
    sys.exit(0)

def ai_distance(d,pos1,pos2):
  # approximate the number of moves from pos1 to pos2, as an entity guesstimates it 
  # could get a better result by using the pathfinder, of course
  if pos1.same_room(pos2):
    this_distance = pos1.distance_within_room(pos2)
  elif pos1.level == pos2.level:
    this_distance = 20*(abs(pos1.r_down-pos2.r_down)+abs(pos1.r_right-pos2.r_right))
    if this_distance==20:
      # get a better approximation
      if pos1.r_down > pos2.r_down:
        dir = DIR_UP
        direct_door = d.l.room_at(pos1).door_up
      elif pos1.r_down < pos2.r_down:
        dir = DIR_DOWN
        direct_door = d.l.room_at(pos1).door_down
      elif pos1.r_right < pos2.r_right:
        dir = DIR_RIGHT
        direct_door = d.l.room_at(pos1).door_right
      elif pos1.r_right > pos2.r_right:
        dir = DIR_LEFT
        direct_door = d.l.room_at(pos1).door_left
      if direct_door==0:
        this_distance = 60
      else:
        door_pos = d.l.room_at(pos1).get_mid_wall(dir)
        door_through_pos = d.l.tile_at(door_pos).to_pos
        this_distance = pos1.distance_within_room(door_pos) + pos2.distance_within_room(door_through_pos) + 1
  else:
    this_distance = 100
  return this_distance

def get_fiery_steps_to_avoid(d,entity): 
  # includes anything that is walkable but it'd rather not step on,
  # including flame and pits
  if entity.info().avoids_fire:
    fiery_steps_to_avoid = d.l.room_at(entity.pos).fiery_tiles + d.l.temporary_fires
  else:
    fiery_steps_to_avoid = []
  if entity.info().avoids_pits:
    for down_right in d.shells[1]:
      this_pos = entity.pos.copy()
      this_pos.right = this_pos.right + down_right[1]
      this_pos.down = this_pos.down + down_right[0]
      if d.l.valid(this_pos) and (d.l.tile_at(this_pos).info().fall_into or d.l.tile_at(this_pos).info().looks_like_floor):
        fiery_steps_to_avoid.append(this_pos)   
  return fiery_steps_to_avoid

class Character_brain(Basic_brain):
  # Only used to guide actions when out of control
  def assess_situation(self,d):
    # If copying this, make sure to honor the 'locked' argument
      self.clear_priorities()
      self.priorities.append(Priority_attack_adjacent(self,urgency=PU_MODERATE,probability=0.5))
      self.priorities.append(Priority_do_nothing(self,urgency=PU_MINOR))
      
# do some psyco bindings 
psyco.bind(Action.resolve)
psyco.bind(make_brain)
psyco.bind(Priority.__cmp__)
psyco.bind(Priority_attack_adjacent.get_action)
psyco.bind(Priority_missile_attack.get_action)
psyco.bind(Priority_non_los_attack.get_action)
psyco.bind(Priority_step_randomly.get_action)
psyco.bind(Priority_use_power_if_meleed.get_action)
psyco.bind(Priority_approach_enemy.get_action)
psyco.bind(Priority_move_to_closest_friend.get_action)
psyco.bind(Priority_power_on_adjacent.get_action)
psyco.bind(Priority_move_around_opponent.get_action)
psyco.bind(Priority_avoid_getting_too_close.get_action)
psyco.bind(Priority_waypoint_wander.get_action)
psyco.bind(Priority_run_away.get_action)